package org.tlang.ast.list.clazz;

import org.tlang.ast.AST;
import org.tlang.ast.ASTLeaf;
import org.tlang.ast.ASTList;
import org.tlang.ast.list.func.ArgumentList;
import org.tlang.context.Location;
import org.tlang.context.SymbolTable;
import org.tlang.context.TypeTable;
import org.tlang.context.ValueTable;
import org.tlang.exception.EvalException;
import org.tlang.exception.TypeException;
import org.tlang.keywords.KeyWords;
import org.tlang.metaclass.TMethod;
import org.tlang.metaclass.TObject;
import org.tlang.type.ClassType;
import org.tlang.type.FuncType;
import org.tlang.type.Type;

import java.util.List;

/**
 * new Class(1, 2)
 */
public class NewClassInvoker extends ASTList {
    private int nest;
    private int index;
    private Location contructorLocation;

    public NewClassInvoker(List<AST> children) {
        super(children);
        index = Location.UNKNOWN;
    }

    private String className() {
        return ((ASTLeaf) child(0)).token().getText();
    }

    private ArgumentList argumentList() {
        return (ArgumentList) child(1);
    }

    @Override
    public String toString() {
        return "(new " + super.toString() + " )";
    }

    @Override
    public void lookup(SymbolTable symbolTable, TypeTable typeTable) {
        // class name
        Location location = symbolTable.where(className());
        if (location == null) {
            throw new EvalException("undefined class: " + this, this);
        }
        nest = location.nest();
        index = location.index();

        // constructor
        ClassType classType = (ClassType) typeTable.get(nest, index);
        contructorLocation = classType.fieldSymbolTable().where(KeyWords.CONSTRUCTOR);

        // arguments
        argumentList().lookup(symbolTable, typeTable);
    }

    @Override
    public Type typeCheck(TypeTable typeTable) throws TypeException {
        Type type = typeTable.get(nest, index);
        if (!type.isClassType()) {
            throw new TypeException("not class type: " + className(), this);
        }

        // 检查参数类型与构造函数类型是否匹配
        if (contructorLocation != null) {
            Type constructorType = ((ClassType) type).fieldTypeTable().get(contructorLocation.nest(), contructorLocation.index());
            if (!constructorType.isFuncType()) {
                throw new TypeException("constructor type error", this);
            }
            FuncType funcType = (FuncType) constructorType;
            argumentList().typeCheck(typeTable);
            if (!funcType.matchParameterTypes(argumentList().types())) {
                throw new TypeException("arguments not match constructor parameters type", this);
            }
        }

        // 返回新建对象的类型
        return type;
    }

    @Override
    public Object eval(ValueTable valueTable) {
        ClassDefine classDefine;
        Object object = valueTable.get(nest, index);
        if (object instanceof ClassDefine) {
            classDefine = (ClassDefine) object;
        } else {
            throw new EvalException("bad class define<" + className() + ">", this);
        }

        TObject tObject = new TObject(classDefine);

        // 运行构造函数
        if (contructorLocation != null) {
            Object constructor = tObject.get(contructorLocation.nest(), contructorLocation.index());
            if (constructor instanceof TMethod) {
                ((TMethod) constructor).execute(valueTable, argumentList());
            }
        }

        return tObject;
    }
}
